package com.kmxd.ams.controller.arrange;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kmxd.ams.app.search.command.CommonAuthCmd;
import com.kmxd.ams.client.es.IDocumentEsSvc;
import com.kmxd.ams.client.oss.vo.EfileRecordVO;
import com.kmxd.ams.client.search.ISearchSvc;
import com.kmxd.ams.client.search.dto.*;
import com.kmxd.ams.client.search.vo.*;
import com.kmxd.ams.core.common.constant.ErrorCode;
import com.kmxd.ams.core.common.enums.ArchOpEnum;
import com.kmxd.ams.core.common.enums.LogLevelEnum;
import com.kmxd.ams.core.common.enums.LogTypeEnum;
import com.kmxd.ams.core.common.enums.UseTypeEnum;
import com.kmxd.ams.core.exception.BizTipException;
import com.kmxd.ams.core.log.annotation.ArchLogParameter;
import com.kmxd.ams.core.log.annotation.AutoLog;
import com.kmxd.ams.core.oss.bo.OssFile;
import com.kmxd.ams.core.result.R;
import com.kmxd.ams.core.util.ConvertUtil;
import com.kmxd.ams.core.util.ExcelUtil;
import com.kmxd.ams.infra.arrange.base.SystemBO;
import com.kmxd.ams.infra.search.entity.DocumentEs;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 数据检索
 *
 * @author nickbi
 * @since 2022-07-20
 */
@RestController
@Tag(name = "数据检索")
@RequestMapping("/arch/search")
@AllArgsConstructor
@Slf4j
public class SearchCtl {

  private ISearchSvc searchSvc;

  /** 组合检索 */
  @Operation(summary = "组合检索")
  @PostMapping("/byCondition")
  @SaCheckLogin
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "组合检索")
  public R<Page> archByCondition(@Validated @RequestBody SearchByConditionDTO req) {
    return R.success(searchSvc.archByCondition(req));
  }

  /** 查询档案库树 */
  @Operation(summary = "模糊检索")
  @PostMapping("/selectArchTree")
  @SaCheckLogin
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "模糊检索")
  public R<List<Tree<String>>> selectArchTree(@Validated @RequestBody SearchByKeywordsDTO req) {
    return R.success(searchSvc.selectArchTree(req));
  }

  /** 快速检索 */
  @Operation(summary = "快速检索")
  @PostMapping("/quick")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "快速检索")
  public R<QuickSearchVO> page(@Validated @RequestBody QuickSearchDTO req) {
    return R.success(searchSvc.quickSearch(req));
  }

  /** 快速检索 */
  @Operation(summary = "高级检索")
  @PostMapping("/advanced")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "高级检索")
  public R<AdvancedSearchVO> advanced(@RequestBody @Validated AdvancedSearchDTO req) {
    return R.success(searchSvc.advancedSearch(req));
  }

  /** 卡片检索 */
  @Operation(summary = "卡片检索")
  @PostMapping("/card")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "卡片检索")
  public R<CardSearchVO> card(@RequestBody @Validated CardSearchDTO req) {
    return R.success(searchSvc.cardSearch(req));
  }

  /** 目录检索目录树 */
  @Operation(summary = "目录检索目录树")
  @PostMapping("/tree")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "目录检索目录树")
  public R<List<Tree<String>>> tree(@RequestBody @Validated CatalogSearchTreeDTO req) {
    return R.success(searchSvc.catalogTree(req));
  }

  /** 目录检索 */
  @Operation(summary = "目录检索")
  @PostMapping("/catalog")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "目录检索")
  public R<CatalogSearchVO> catalog(@RequestBody @Validated CatalogSearchDTO req) {
    return R.success(searchSvc.catalogSearch(req));
  }

  /** 检索详情 */
  @Operation(summary = "检索详情")
  @PostMapping("/view")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "检索详情")
  public R<SearchViewVO> view(@RequestBody @Validated SearchViewDTO req) {
    return R.success(searchSvc.view(req));
  }

  /** 获取电子文件权限 */
  @Operation(summary = "获取电子文件权限")
  @PostMapping("/getAuth")
  @AutoLog(
      module = "获取电子文件权限",
      type = LogTypeEnum.SEARCH,
      level = LogLevelEnum.HIGH,
      title = "获取电子文件权限")
  public R<SearchAuthVO> getAuth(@RequestBody @Validated SearchAuthQueryDTO req) {
    return R.success(searchSvc.getAuth(req));
  }

  /** 文件下载 */
  @Operation(summary = "文件下载")
  @GetMapping("/download")
  @AutoLog(
      module = "文件下载",
      type = LogTypeEnum.ELECTRONIC_RECORD,
      level = LogLevelEnum.HIGH,
      title = "文件下载",
      opType = "下载")
  @SneakyThrows
  public void download(SearchFileDownloadDTO req, HttpServletResponse response) {
    EfileRecordVO recordVO = searchSvc.download(req);
    response.reset();
    response.setContentType("application/octet-stream;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    response.setHeader(
        "Content-Disposition",
        "attachment; filename="
            + URLEncoder.encode(recordVO.getTitle(), StandardCharsets.UTF_8.name()));
    response.addHeader("Content-Length", String.valueOf(recordVO.getFile().length()));
    try (ServletOutputStream out = response.getOutputStream()) {
      IoUtil.write(out, true, FileUtil.readBytes(recordVO.getFile()));
    } catch (IOException e) {
      log.error("{} 文件下载失败", recordVO, e);
    }
  }

  /** 导出Zip包 */
  @Operation(summary = "导出Zip包")
  @GetMapping("/exportZip")
  @AutoLog(
      module = "电子文件表",
      type = LogTypeEnum.ELECTRONIC_RECORD,
      level = LogLevelEnum.HIGH,
      title = "导出Zip包",
      opType = "下载")
  @SneakyThrows
  public void exportZip(SearchFileZipDownloadDTO req, HttpServletResponse response) {
    OssFile ossFile = searchSvc.downloadZip(req);
    if (ObjectUtil.isEmpty(ossFile)
        || ObjectUtil.isEmpty(ossFile.getFile())
        || ossFile.getFile().length() == 0) {
      throw BizTipException.instance(ErrorCode.E_FILE_NOT_MOUNT, "当前所选档案电子文件未找到!");
    }
    if (ObjectUtil.isNotNull(ossFile)) {
      response.reset();
      response.setContentType("application/octet-stream;charset=UTF-8");
      response.setCharacterEncoding("UTF-8");
      response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
      response.setHeader(
          "Content-Disposition",
          "attachment; filename="
              + URLEncoder.encode(ossFile.getName(), StandardCharsets.UTF_8.name()));
      response.addHeader("Content-Length", String.valueOf(ossFile.getFile().length()));
      try (ServletOutputStream out = response.getOutputStream()) {
        IoUtil.write(out, true, FileUtil.readBytes(ossFile.getFile()));
      } catch (IOException e) {
        log.error("{} 文件下载失败", ossFile, e);
      }
    }
  }

  /** 预览 */
  @SneakyThrows
  @Operation(summary = "预览")
  @GetMapping("/preview")
  @AutoLog(module = "电子文件表", title = "预览", opType = "浏览")
  public void preview(SearchFilePreviewDTO req, HttpServletResponse response) {
    CommonAuthCmd authCmd = SpringUtil.getBean(CommonAuthCmd.class);
    IDocumentEsSvc documentSvc = SpringUtil.getBean(IDocumentEsSvc.class);
    DocumentEs data = documentSvc.getById(req.getId(), req.getTableId());
    boolean browserFileAuth =
        authCmd.checkAuth(req.getArchTypeId(), data, UseTypeEnum.BROWSER_FILE);
    if (!BooleanUtil.isTrue(browserFileAuth)) {
      throw BizTipException.instance(ErrorCode.SEARCH_NO_AUTH, "无原文浏览权限");
    }
    EfileRecordVO efileRecordVO =
        Optional.ofNullable(searchSvc.preview(req))
            .orElseThrow(() -> BizTipException.instance(ErrorCode.NOT_FOUND, "电子文件不存在!"));
    if (!FileUtil.exist(efileRecordVO.getFile())) {
      BizTipException.throwException(ErrorCode.E_FILE_NOT_MOUNT, "电子文件不存在!");
    }
    if (Objects.isNull(efileRecordVO.getShowFilePath())) {
      BizTipException.throwException(ErrorCode.E_FILE_NOT_CONVERT, "文件未转换!");
    }
    String filename = efileRecordVO.getSaveFileName();
    response.setContentType("application/octet-stream;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    response.setHeader(
        "Content-Disposition",
        "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
    FileUtil.writeToStream(efileRecordVO.getFile(), response.getOutputStream());
  }

  /** 导出 */
  @Operation(summary = "快速检索导出")
  @PostMapping("/quickExport")
  @AutoLog(module = "数据检索", type = LogTypeEnum.ARCHIVE, level = LogLevelEnum.HIGH, title = "导出")
  @ArchLogParameter(op = ArchOpEnum.DATA_EXPORT, content = "导出")
  @SneakyThrows
  public void export(@RequestBody QuickSearchExportDTO req, HttpServletResponse response) {
    List<String> list = searchSvc.quickSearchExport(req);
    outputZipFile(response, list);
  }

  /** 导出 */
  @Operation(summary = "高级检索导出")
  @PostMapping("/advancedExport")
  @AutoLog(module = "数据检索", type = LogTypeEnum.ARCHIVE, level = LogLevelEnum.HIGH, title = "导出")
  @ArchLogParameter(op = ArchOpEnum.DATA_EXPORT, content = "导出")
  @SneakyThrows
  public void advancedExport(
      @RequestBody AdvancedSearchExportDTO req, HttpServletResponse response) {
    List<String> list = searchSvc.advancedSearchExport(req);
    outputZipFile(response, list);
  }

  /** 导出 */
  @Operation(summary = "卡片检索导出")
  @PostMapping("/cardExport")
  @AutoLog(module = "数据检索", type = LogTypeEnum.ARCHIVE, level = LogLevelEnum.HIGH, title = "导出")
  @ArchLogParameter(op = ArchOpEnum.DATA_EXPORT, content = "导出")
  @SneakyThrows
  public void cardExport(@RequestBody CardSearchExportDTO req, HttpServletResponse response) {
    List<String> list = searchSvc.cardSearchExport(req);
    outputZipFile(response, list);
  }

  /** 导出 */
  @Operation(summary = "目录检索导出")
  @PostMapping("/catalogExport")
  @AutoLog(module = "数据检索", type = LogTypeEnum.ARCHIVE, level = LogLevelEnum.HIGH, title = "导出")
  @ArchLogParameter(op = ArchOpEnum.DATA_EXPORT, content = "导出")
  @SneakyThrows
  public void catalogExport(@RequestBody CatalogSearchExportDTO req, HttpServletResponse response) {
    List<String> list = searchSvc.catalogSearchExport(req);
    outputZipFile(response, list);
  }

  /** 条目打印 */
  @Operation(summary = "条目打印")
  @PostMapping("/print")
  @AutoLog(module = "数据检索", type = LogTypeEnum.ARCHIVE, level = LogLevelEnum.HIGH, title = "导出")
  @ArchLogParameter(op = ArchOpEnum.DATA_EXPORT, content = "导出")
  @SneakyThrows
  public void print(@RequestBody CatalogSearchExportDTO req, HttpServletResponse response) {
    List<? extends SystemBO> list = searchSvc.catalogSearchData(req);

    String filename = "文件" + "_" + System.currentTimeMillis() + ".xlsx";
    response.setContentType("application/vnd.ms-excel");
    response.setCharacterEncoding("UTF-8");
    response.setHeader(
        "Content-Disposition",
        "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));

    List<String> header =
        req.getFields().stream()
            .filter(item -> ObjectUtil.isNotEmpty(item.getDbFieldName()))
            .map(item -> item.getDbFieldName())
            .collect(Collectors.toList());
    List<String> keys =
        req.getFields().stream()
            .filter(item -> ObjectUtil.isNotEmpty(item.getDbFieldKey()))
            .map(item -> item.getDbFieldKey())
            .collect(Collectors.toList());
    EasyExcel.write(response.getOutputStream())
        // 这里放入动态头
        .head(ExcelUtil.head(header))
        .sheet("数据")
        .doWrite(ExcelUtil.detail(ConvertUtil.toList(list), keys));
  }

  /** 快速检索 */
  @Operation(summary = "列表")
  @PostMapping("/list")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "列表")
  public R<List<SearchViewVO>> list(@RequestBody @Validated SearchViewListQueryDTO req) {
    return R.success(searchSvc.list(req));
  }

  /** 快速检索 */
  @Operation(summary = "导出列表")
  @PostMapping("/exportList")
  @AutoLog(module = "数据检索", type = LogTypeEnum.SEARCH, level = LogLevelEnum.LOW, title = "导出列表")
  public void exportList(
      @RequestBody @Validated SearchViewListQueryDTO req, HttpServletResponse response)
      throws IOException {
    List<String> list = searchSvc.exportList(req);
    outputZipFile(response, list);
  }

  private void outputZipFile(HttpServletResponse response, List<String> list) throws IOException {
    if (ObjectUtil.isEmpty(list)) {
      throw BizTipException.instance(ErrorCode.NOT_FOUND, "未查询到导出数据信息");
    }
    String filename = SystemClock.now() + ".zip";
    response.setContentType("application/octet-stream");
    response.setCharacterEncoding("UTF-8");
    response.setHeader(
        "Content-Disposition",
        "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
    InputStream[] inputStreams =
        list.stream().map(path -> FileUtil.getInputStream(path)).toArray(InputStream[]::new);
    String[] fileName = list.stream().map(path -> FileUtil.getName(path)).toArray(String[]::new);
    ZipUtil.zip(response.getOutputStream(), fileName, inputStreams);
  }
}
